// Copyright(c) Sonosemi Medical Ltd. Embedded Software Team.
// Author: Zhang Yisong(zhangyisong@sonosemi.com)

#pragma once
#include <type_traits>
#include <zephyr/drivers/gpio.h>
#include <zpp/assert.hpp>
#include <zpp/callback.hpp>
#include <zpp/error.hpp>
#include <zpp/result.hpp>
namespace zpp {

struct gpio {
  enum class input_flag {
    pull_up = (GPIO_PULL_UP | GPIO_INPUT),
    pull_down = (GPIO_PULL_DOWN | GPIO_INPUT),
    no_pull = GPIO_INPUT
  };
  enum class output_flag {
    low = GPIO_OUTPUT_LOW,
    high = GPIO_OUTPUT_HIGH,
    active = GPIO_OUTPUT_ACTIVE,
    inactive = GPIO_OUTPUT_INACTIVE,
  };
  enum class interrupt_flag {
    edge_to_active = GPIO_INT_EDGE_TO_ACTIVE,
    edge_to_inactive = GPIO_INT_EDGE_TO_INACTIVE,
  };
  struct spec : protected gpio_dt_spec {
  public:
    constexpr spec(const gpio_dt_spec &&dt_spec) {
      pin = dt_spec.pin;
      port = dt_spec.port;
      dt_flags = dt_spec.dt_flags;
    }
    constexpr spec(const gpio_dt_spec &dt_sepc) {
      pin = dt_sepc.pin;
      port = dt_sepc.port;
      dt_flags = dt_sepc.dt_flags;
    }
    constexpr spec(const device *port, gpio_pin_t pin, gpio_dt_flags_t flags) {
      this->pin = pin;
      this->port = port;
      this->dt_flags = flags;
    }
    class callback : public gpio_callback {
    public:
      using func_t = zpp::callback<void()>;
      callback() = default;
      callback(func_t &&cb) { m_func = std::move(cb); }
      template <typename t_callable>
      auto init(spec &io, t_callable func) -> void {
        m_func = func;
        gpio_init_callback(this, handler, BIT(io.pin));
      }

    private:
      static auto handler(const device *dev, gpio_callback *cb,
                          uint32_t pin) -> void {
        auto obj = reinterpret_cast<spec::callback *>(cb);
        obj->m_func();
      }

      func_t m_func;
    };
    auto pin_interrupt_configure(interrupt_flag flag) -> zpp::error {
      const auto ret =
          gpio_pin_interrupt_configure_dt(this, static_cast<int>(flag));
      return to_error_code(ret);
    }
    template <typename t_callable>
    auto pin_interrupt_configure(interrupt_flag flag,
                                 t_callable func) -> zpp::error {
      if (const auto ret =
              gpio_pin_interrupt_configure_dt(this, static_cast<int>(flag));
          ret != 0) {
        return to_error_code(ret);
      }
      return this->add_callback(func);
    }
    template <typename t_flag>
      requires std::disjunction_v<std::is_same<t_flag, input_flag>,
                                  std::is_same<t_flag, output_flag>>
    auto pin_configure(t_flag flag) -> zpp::error {
      const auto ret = gpio_pin_configure_dt(this, static_cast<int>(flag));
      return error{to_error_code(ret)};
    }
    auto pin_set(bool value) -> result<int> {
      if (const auto ret = gpio_pin_set_dt(this, value); ret < 0) {
        return error{to_error_code(ret)};
      } else {
        return ret;
      }
    }
    auto operator=(bool value) -> result<int> { return pin_set(value).value(); }
    auto pin_get() const -> bool { return gpio_pin_get_dt(this); }

    auto pin_toggle() -> bool { return gpio_pin_toggle_dt(this); }
    template <typename t_callable>
    auto add_callback(t_callable callback) -> error {
      m_cb.init(*this, callback);
      return error_code(gpio_add_callback_dt(this, &m_cb));
    }

    auto remove_callback(gpio_callback &callback) -> error {
      return error_code(gpio_remove_callback_dt(this, &m_cb));
    }

  private:
    spec::callback m_cb;
  };
};
} // namespace zpp
